/*
 * Copyright 2007 Transmogrify, LLC.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.xmog.web.util;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.log4j.Logger;

/**
 * Utilities for working with the reflection API. Includes a non-reflection
 * method, {@link #findClassesIn(String)}, which can find all classes in a
 * given package (reflection cannot help us there).
 * 
 * @author <a href="mailto:maa@xmog.com">Mark Allen</a>
 */
@SuppressWarnings("unchecked")
public class ReflectionUtils {
	private static final Logger log = Logger.getLogger(ReflectionUtils.class);

	/**
	 * Given a package name, scans the classpath to find all classes in that
	 * package.
	 * 
	 * Note that found classes are only those loaded by <b>this class'</b>
	 * classloader - we cannot, without changing this method to query different
	 * classloaders, pull in system jars in a web environment (for example).
	 * 
	 * Thanks to <a
	 * href="http://forum.java.sun.com/thread.jspa?threadID=757391&messageID=4326850">Primor</a>
	 * for the original code.
	 * 
	 * @param packageName The base package from which classes are scanned.
	 * @return A list of all classes contained in the given package.
	 * @throws ClassDetectionException If an error occurs while attempting to
	 *         scan for classes.
	 */
	public static Set<Class> findClassesIn(String packageName) throws ClassDetectionException {
		Set<Class> classes = new HashSet<Class>();

		try {
			String packagePath = packageName.replace('.', '/');
			URL[] classpath = ((URLClassLoader) ReflectionUtils.class.getClassLoader()).getURLs();

			for (URL url : classpath) {
				if (log.isTraceEnabled()) {
					log.trace("Searching for classes in " + url + "...");
				}

				File file = new File(url.toURI());
				if (file.getPath().endsWith(".jar")) {
					JarFile jarFile = new JarFile(file);
					for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) {
						String entryName = (entries.nextElement()).getName();
						if (entryName.matches(packagePath + "/\\w*\\.class")) {
							String className = entryName.replace('/', '.').substring(0,
									entryName.lastIndexOf('.'));
							classes.add(Class.forName(className));
						}
					}
				} else { // directory
					File packageDirectory = new File(file.getPath() + "/" + packagePath);
					File[] files = packageDirectory.listFiles();
					if (files != null) {
						for (File f : files) {
							if (f.getPath().endsWith(".class")) {
								String className = packageName + "."
										+ f.getName().substring(0, f.getName().lastIndexOf('.'));
								classes.add(Class.forName(className));
							}
						}
					}
				}
			}
		} catch (Throwable t) {
			throw new ClassDetectionException(t);
		}

		if (log.isTraceEnabled()) {
			log.trace("Found " + classes.size() + " classes in package '" + packageName + "'.");
		}

		return classes;
	}

	/**
	 * Given a method, reflectively builds a simple string representing it.
	 * Formatted as
	 * {@code SimpleClassName.methodName(arg types separated by commas and prefixed with Xmog Web annotations if present)}.
	 * 
	 * @param method The method from which to build a string representation.
	 * @return A string representation of the given method.
	 */
	public static String methodToString(Method method) {
		StringBuffer buffer = new StringBuffer();
		buffer.append(method.getDeclaringClass().getSimpleName());
		buffer.append(".");
		buffer.append(method.getName());
		buffer.append("(");

		Class[] parameterClasses = method.getParameterTypes();
		for (int i = 0; i < parameterClasses.length; i++) {
			Annotation[][] annotations = method.getParameterAnnotations();
			for (int j = 0; j < annotations[i].length; j++) {
				buffer.append("@");
				buffer.append(annotations[i][j].annotationType().getSimpleName());
				buffer.append(" ");
			}

			buffer.append(parameterClasses[i].getSimpleName());
			if (i < parameterClasses.length - 1) {
				buffer.append(", ");
			}
		}

		buffer.append(")");
		return buffer.toString();
	}
}
